/*
 * Copyright 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.dev.jas.impl;

import com.google.gwt.core.client.impl.ArtificialRescue;
import com.google.gwt.dev.jdt.SafeASTVisitor;
import com.google.gwt.dev.util.Empty;
import com.google.gwt.dev.util.JsniRef;
import com.google.gwt.dev.util.collect.Lists;

import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.ast.Annotation;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
import org.eclipse.jdt.internal.compiler.impl.StringConstant;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReasons;
import org.eclipse.jdt.internal.compiler.lookup.ProblemReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Checks the validity of ArtificialRescue annotations.
 * 
 * <ul>
 * <li>(1) The ArtificialRescue annotation is only used in generated code.</li>
 * <li>(2) The className value names a type known to GWT.</li>
 * <li>(3) The methods and fields of the type are known to GWT.</li>
 * </ul>
 */
public abstract class ArtificialRescueChecker {

  /**
   * Represents a single
   * {@link com.google.gwt.core.client.impl.ArtificialRescue.Rescue}.
   * <p>
   * Only public so it can be used by
   * {@link com.google.gwt.dev.jjs.impl.GenerateJavaAST}.
   */
  public static class RescueData {
    private static final RescueData[] EMPTY_RESCUEDATA = new RescueData[0];

    public static RescueData[] createFromAnnotations(Annotation[] annotations) {
      RescueData[] result = EMPTY_RESCUEDATA;
      for (Annotation a : annotations) {
        ReferenceBinding binding = (ReferenceBinding) a.resolvedType;
        String name = CharOperation.toString(binding.compoundName);
        if (!name.equals(ArtificialRescue.class.getName())) {
          continue;
        }
        return createFromArtificialRescue(a);
      }
      return result;
    }

    public static RescueData[] createFromArtificialRescue(Annotation artificialRescue) {
      Object[] values = null;
      RescueData[] result = EMPTY_RESCUEDATA;
      for (ElementValuePair pair : artificialRescue.computeElementValuePairs()) {
        if ("value".equals(String.valueOf(pair.getName()))) {
          Object value = pair.getValue();
          if (value instanceof AnnotationBinding) {
            values = new Object[]{value};
          } else {
            values = (Object[]) value;
          }
          break;
        }
      }
      assert values != null;
      if (values.length > 0) {
        result = new RescueData[values.length];
        for (int i = 0; i < result.length; ++i) {
          result[i] = createFromRescue((AnnotationBinding) values[i]);
        }
      }
      return result;
    }

    private static RescueData createFromRescue(AnnotationBinding rescue) {
      String className = null;
      boolean instantiable = false;
      String[] methods = Empty.STRINGS;
      String[] fields = Empty.STRINGS;
      for (ElementValuePair pair : rescue.getElementValuePairs()) {
        String name = String.valueOf(pair.getName());
        if ("className".equals(name)) {
          className = ((StringConstant) pair.getValue()).stringValue();
        } else if ("instantiable".equals(name)) {
          BooleanConstant value = (BooleanConstant) pair.getValue();
          instantiable = value.booleanValue();
        } else if ("methods".equals(name)) {
          methods = getValueAsStringArray(pair.getValue());
        } else if ("fields".equals(name)) {
          fields = getValueAsStringArray(pair.getValue());
        } else {
          assert false : "Unknown ArtificialRescue field";
        }
      }
      assert className != null;
      return new RescueData(className, instantiable, fields, methods);
    }

    private static String[] getValueAsStringArray(Object value) {
      if (value instanceof StringConstant) {
        return new String[]{((StringConstant) value).stringValue()};
      }
      Object[] values = (Object[]) value;
      String[] toReturn = new String[values.length];
      for (int i = 0; i < toReturn.length; ++i) {
        toReturn[i] = ((StringConstant) values[i]).stringValue();
      }
      return toReturn;
    }

    private final String className;
    private final String[] fields;
    private final boolean instantiable;
    private final String[] methods;

    RescueData(String className, boolean instantiable, String[] fields, String[] methods) {
      this.className = className;
      this.instantiable = instantiable;
      this.methods = methods;
      this.fields = fields;
    }

    public String getClassName() {
      return className;
    }

    public String[] getFields() {
      return fields;
    }

    public String[] getMethods() {
      return methods;
    }

    public boolean isInstantiable() {
      return instantiable;
    }
  }
}
